unit PtSettingsEditorFactory;

interface

uses
{$IFDEF VCL}
  Controls, Forms,
{$ELSE}
  FMX.Types, FMX.Controls, FMX.SpinBox,
{$ENDIF}
  Classes, TypInfo, Generics.Collections;

type
  TSystemToTitle = function (Str: string): string of object;

  ISettingsTypeEditor = interface
    ['{CC34F8FE-A2E6-4568-9B79-88682A3FB3E9}']
    function  GetValue: Variant;
    procedure SetValue(Value: Variant);
  end;

  TSettingsTypeEditorCreator = class
  protected
    fTypeKind: TTypeKind;
{$IFDEF VCL}
    function  CreateFrame(Owner, Parent: TWinControl; TypeInfo: PTypeInfo): TWinControl; virtual; abstract;
{$ELSE}
    function  CreateFrame(Owner, Parent: TControl; TypeInfo: PTypeInfo): TControl; virtual; abstract;
{$ENDIF}
  public
    constructor Create(ATypeKind: TTypeKind);
    property  TypeKind: TTypeKind read fTypeKind;
  end;

  TSettingsTypeEditorFactory = class
  private
    fCreators: TObjectList<TSettingsTypeEditorCreator>;
  protected
    procedure AddCreator(FrameCreator: TSettingsTypeEditorCreator);
  public
    constructor Create;
    destructor  Destroy; override;
{$IFDEF VCL}
    function  CreateFrame(Owner, Parent: TWinControl; TypeInfo: PTypeInfo; const TypeKind: TTypeKind): TWinControl;
{$ELSE}
    function  CreateFrame(Owner, Parent: TControl; TypeInfo: PTypeInfo; const TypeKind: TTypeKind): TControl;
{$ENDIF}
  end;

var
  EditorFactory: TSettingsTypeEditorFactory;

implementation

uses
{$IFDEF VCL}
  StdCtrls, Vcl.CheckLst, Vcl.Samples.Spin,
{$ELSE}
  FMX.Edit, FMX.ListBox, FMX.StdCtrls,
{$ENDIF}
  PtSettings, PtSettingsControls;

type
  TSimpleCreator = class(TSettingsTypeEditorCreator)
{$IFDEF VCL}
  private
    fClass: TWinControlClass;
  protected
    function CreateFrame(Owner, Parent: TWinControl; TypeInfo: PTypeInfo): TWinControl; override;
  public
    constructor Create(ATypeKind: TTypeKind; AClass: TWinControlClass);
{$ELSE}
  private
    fClass: TFmxObjectClass;
  protected
    function CreateFrame(Owner, Parent: TControl; TypeInfo: PTypeInfo): TControl; override;
  public
    constructor Create(ATypeKind: TTypeKind; AClass: TFmxObjectClass);
{$ENDIF}
  end;

  //  Editors

{$IFDEF VCL}
  TIntegerEdit = class(TSpinEdit, ISettingsTypeEditor)
{$ELSE}
  TIntegerEdit = class(TSpinBox, ISettingsTypeEditor)
{$ENDIF}
  public
    function GetValue: Variant;
    procedure SetValue(Value: Variant);
  end;

  TTextEdit = class(TEdit, ISettingsTypeEditor)
  public
    function GetValue: Variant;
    procedure SetValue(Value: Variant);
  end;

  TEnumEdit = class(TComboBox, ISettingsTypeEditor)
  public
    function GetValue: Variant;
    procedure SetValue(Value: Variant);
  end;

  TBoolEdit = class(TCheckBox, ISettingsTypeEditor)
  public
    function GetValue: Variant;
    procedure SetValue(Value: Variant);
  end;

  TEnumCreator = class(TSettingsTypeEditorCreator)
  protected
{$IFDEF VCL}
    function CreateFrame(Owner: TWinControl; Parent: TWinControl; ATypeInfo: PTypeInfo): TWinControl; override;
{$ELSE}
    function CreateFrame(Owner: TControl; Parent: TControl; ATypeInfo: PTypeInfo): TControl; override;
{$ENDIF}
  end;

{$IFDEF VCL}
  TSetEdit = class(TCheckListBox, ISettingsTypeEditor)
{$ELSE}
  TSetEdit = class(TListBox, ISettingsTypeEditor)
{$ENDIF}
  public
    function GetValue: Variant;
    procedure SetValue(Value: Variant);
  end;

  TSetCreator = class(TSettingsTypeEditorCreator)
  protected
{$IFDEF VCL}
    function CreateFrame(Owner: TWinControl; Parent: TWinControl; ATypeInfo: PTypeInfo): TWinControl; override;
{$ELSE}
    function CreateFrame(Owner: TControl; Parent: TControl; ATypeInfo: PTypeInfo): TControl; override;
{$ENDIF}
  end;

{ TSettingsTypeEditorCreator }

constructor TSettingsTypeEditorCreator.Create(ATypeKind: TTypeKind);
begin
  inherited Create;
  fTypeKind := ATypeKind;
end;

{ TSettingsTypeEditorFactory }

procedure TSettingsTypeEditorFactory.AddCreator(FrameCreator: TSettingsTypeEditorCreator);
begin
  fCreators.Add( FrameCreator )
end;

constructor TSettingsTypeEditorFactory.Create;

{$IFDEF VCL}
  procedure AddSimpleCreator(TypeKind: TTypeKind; ControlClass: TWinControlClass);
{$ELSE}
  procedure AddSimpleCreator(TypeKind: TTypeKind; ControlClass: TFmxObjectClass);
{$ENDIF}
  begin
    AddCreator(TSimpleCreator.Create(TypeKind, ControlClass));
  end;

begin
  inherited;
  fCreators := TObjectList<TSettingsTypeEditorCreator>.Create;
  //  tkInteger
  AddSimpleCreator(tkInteger, TIntegerEdit);
  AddSimpleCreator(tkString,  TTextEdit);
  AddSimpleCreator(tkLString, TTextEdit);
  AddSimpleCreator(tkWString, TTextEdit);
  AddSimpleCreator(tkUString, TTextEdit);
  AddCreator(TEnumCreator.Create(tkEnumeration));
  AddCreator(TSetCreator.Create(tkSet));
{
      , tkChar, tkWChar:
        SetOrdProp( Settings, Name, Value );

      tkFloat:
        SetFloatProp( Settings, Name, Value );

      tkInt64:
		    SetInt64Prop( Settings, Name, Value );
}
end;

{$IFDEF VCL}
function TSettingsTypeEditorFactory.CreateFrame(Owner, Parent: TWinControl; TypeInfo: PTypeInfo; const TypeKind: TTypeKind): TWinControl;
{$ELSE}
function TSettingsTypeEditorFactory.CreateFrame(Owner, Parent: TControl; TypeInfo: PTypeInfo; const TypeKind: TTypeKind): TControl;
{$ENDIF}
var
  i : integer;
  Creator : TSettingsTypeEditorCreator;
begin
  for i := 0 to fCreators.Count-1 do
    begin
      Creator := fCreators[i];
      if  Creator.TypeKind = TypeKind then
        begin
          Result  := Creator.CreateFrame(Owner, Parent, TypeInfo);
          exit;
        end;
    end;
  raise EPtSettings.Create('No suitable editor found for data type ' + TypeInfo.Name + '.');
end;

destructor TSettingsTypeEditorFactory.Destroy;
begin
  fCreators.Free;
  inherited;
end;

{ TSimpleCreator }

{$IFDEF VCL}
constructor TSimpleCreator.Create(ATypeKind: TTypeKind; AClass: TWinControlClass);
{$ELSE}
constructor TSimpleCreator.Create(ATypeKind: TTypeKind; AClass: TFmxObjectClass);
{$ENDIF}
begin
  inherited Create(ATypeKind);
  fClass    := AClass;
end;

{$IFDEF VCL}
function TSimpleCreator.CreateFrame(Owner, Parent: TWinControl; TypeInfo: PTypeInfo): TWinControl;
begin
  Result  := fClass.Create(Owner);
  Result.Parent := Parent;
end;
{$ELSE}
function TSimpleCreator.CreateFrame(Owner, Parent: TControl; TypeInfo: PTypeInfo): TControl;
begin
  Result  := fClass.Create(Owner) as TControl;
  Result.Parent := Parent;
end;
{$ENDIF}

{ TIntegerEdit }

function TIntegerEdit.GetValue: Variant;
begin
  Result  := Self.Value
end;

procedure TIntegerEdit.SetValue(Value: Variant);
begin
  Self.Value  := Value
end;

{ TTextEdit }

function TTextEdit.GetValue: Variant;
begin
  Result  := Self.Text
end;

procedure TTextEdit.SetValue(Value: Variant);
begin
  Self.Text := Value
end;

{ TEnumEdit }

function TEnumEdit.GetValue: Variant;
begin
{$IFDEF VCL}
  Result  := Text
{$ELSE}
  Result  := Items[ItemIndex]
{$ENDIF}
end;

procedure TEnumEdit.SetValue(Value: Variant);
begin
  ItemIndex := Items.IndexOf(Value)
end;

{ TBoolEdit }

function TBoolEdit.GetValue: Variant;
begin
{$IFDEF VCL}
  Result  := Checked
{$ELSE}
  Result  := IsChecked
{$ENDIF}
end;

procedure TBoolEdit.SetValue(Value: Variant);
begin
{$IFDEF VCL}
  Checked := Value;
{$ELSE}
  IsChecked := Value;
{$ENDIF}
end;

{ TEnumCreator }

{$IFDEF VCL}
function TEnumCreator.CreateFrame(Owner, Parent: TWinControl; ATypeInfo: PTypeInfo): TWinControl;
{$ELSE}
function TEnumCreator.CreateFrame(Owner, Parent: TControl; ATypeInfo: PTypeInfo): TControl;
{$ENDIF}
var
  EnumEdit: TEnumEdit;
begin
  if  ATypeInfo = TypeInfo(Boolean) then
    begin
      Result  := TBoolEdit.Create(Owner);
      Result.Parent := Parent;
      TBoolEdit(Result).Text  := ' ';
    end
  else
    begin
      EnumEdit  := TEnumEdit.Create(Owner);
      EnumEdit.Parent := Parent;
{$IFDEF VCL}
      EnumEdit.Style  := csDropDownList;
{$ELSE}
//      EnumEdit.Style  := csDropDownList;
{$ENDIF}
      ListEnumValues(ATypeInfo, EnumEdit.Items);
      Result    := EnumEdit;
    end;
end;

{ TSetEdit }

function TSetEdit.GetValue: Variant;
var
  i: integer;
  Strings: TStringList;
begin
  Strings := TStringList.Create;
  try
{$IFDEF VCL}
    for i := 0 to Items.Count - 1 do
      if  Checked[i]  then
        Strings.Add(Items[i]);
{$ELSE}
{$ENDIF}
    Result  := Strings.CommaText;
  finally
    Strings.Free;
  end;
end;

procedure TSetEdit.SetValue(Value: Variant);
var
  i: integer;
  Strings: TStringList;
begin
  Strings := TStringList.Create;
  try
    Strings.CommaText := Value;
{$IFDEF VCL}
    for i := 0 to Items.Count - 1 do
      Checked[i]  := Strings.IndexOf(Items[i]) >= 0;
{$ELSE}
{$ENDIF}
  finally
    Strings.Free;
  end;
end;

{ TSetCreator }

{$IFDEF VCL}
function TSetCreator.CreateFrame(Owner, Parent: TWinControl; ATypeInfo: PTypeInfo): TWinControl;
{$ELSE}
function TSetCreator.CreateFrame(Owner, Parent: TControl; ATypeInfo: PTypeInfo): TControl;
{$ENDIF}

  procedure ListAllValues(Strings: TStrings);
  var
    TypeInfo: PTypeInfo;
    TypeData: PTypeData;
    i: integer;
  begin
    Strings.Clear;
    TypeInfo  := GetTypeData(ATypeInfo).CompType^;
    TypeData  := GetTypeData(TypeInfo);
    for i := TypeData.MinValue to TypeData.MaxValue do
      Strings.AddObject(GetEnumName(TypeInfo, i), TObject(i));
  end;

var
  SetEdit: TSetEdit;
begin
  SetEdit := TSetEdit.Create(Owner);
  SetEdit.Parent  := Parent;
  ListAllValues(SetEdit.Items);
  SetEdit.Height  := SetEdit.Canvas.TextHeight('Xy') * (SetEdit.Items.Count + 1);
  Result  := SetEdit;
end;

initialization
  EditorFactory := TSettingsTypeEditorFactory.Create;

finalization
  EditorFactory.Free;

end.
